Improve message markdown display and formatting - #7
Merged
Conversation
tlongwell-block
added a commit
that referenced
this pull request
Mar 10, 2026
* origin/main: Add desktop Home feed (#12) Add desktop Playwright e2e harness (#11) Update desktop icon and persist window state (#9) feat: add channel creation flow (#8) Improve message markdown display and formatting (#7) feat(desktop): connect chat to relay (#6) docs(readme): clarify desktop setup (#4) feat: add desktop app (#3) # Conflicts: # crates/sprout-test-client/tests/e2e_rest_api.rs
tlongwell-block
added a commit
that referenced
this pull request
Mar 16, 2026
Crossfire round 1: codex 4/10, opus 8/10. All critical issues fixed: Security (critical): - Force channel_id=None for kind:1059 gift wraps — prevents channel-scoped storage that would bypass #p AUTH-gating (codex finding #1) Correctness: - NIP-50 pagination loop — keep fetching Typesense pages until limit met or result set exhausted, capped at MAX_SEARCH_PAGES=5 (codex finding #2) - Push authors/since/until to Typesense filter_by — post-filtering is now a correction step, not the primary filter (codex + opus suggestion) - NIP-10 root tag validation — reject events where client-supplied root diverges from server-resolved ancestry (codex finding #3) Clarity: - Consolidate #p gating into single P_GATED_KINDS check (opus suggestion #7) - filter.clone() → std::slice::from_ref(filter) (opus suggestion #1) - Remove no-op get_events_by_ids test, add debug_assert (opus #3, #5)
tlongwell-block
added a commit
that referenced
this pull request
May 14, 2026
Adds a Settings → Agent Provider panel to the desktop GUI for configuring the `sprout-agent` provider, model, API key, and behavior knobs. Settings are encrypted at rest with NIP-44 self-encryption (the user's own nostr key) and injected into the sprout-agent child's env at spawn time. The panel also gains automatic provider-URL detection based on the API key format the user pastes. ## Backend (`desktop/src-tauri/src/commands/agent_provider_settings/`) - `mod.rs` — IPC types + plaintext `StoredSettings` (Drop zeroizes `api_key`; no Debug derive on input/stored). v2 envelope binds the plaintext to its owner pubkey for rollback / envelope-swap protection. - `storage.rs` — envelope read/write, NIP-44 encrypt/decrypt with `Zeroizing<String>` for plaintext, atomic-rename writes, file-size cap, `normalize_origin` (rejects non-loopback `http://`, userinfo, query, fragment), `validate_input` (provider whitelist, control-char rejection, size caps for key/model/base_url/system_prompt, positive- int knobs). `validate_stored` mirrors the same rules on the decrypted blob at spawn time — fails closed on a rolled-back pre-validation envelope so a redirected `http://api.example.com/v1` cannot escape. - `commands.rs` — `get_*`, `save_*`, `delete_*`, `get_*_env_presence` Tauri commands. The save command trims whitespace + zeroes the input api_key before validation can early-return. - `spawn.rs` — `LoadForSpawn` enum + `EnvPairs` newtype whose Drop zeroizes every value buffer. `apply_to_command` hands each env pair to `Command::env` by reference, zeroizing the local buffer after. Spawn policy: Ok → strip OWNED_AGENT_ENV_VARS + ACP-level vars then inject; None → no-op; IdentityMismatch / Error → fail closed (strip inherited owned vars, inject nothing). - `tests.rs` — round-trip envelope I/O, identity-rotation, save-time validation (oversized prompt, zero timeouts, tiny history bytes, unknown provider, control chars, oversized fields, api-key whitespace trim, owner_pubkey v2), `apply_to_command` × `LoadForSpawn` matrix (Ok/None/IdentityMismatch-fails-closed/Error-fails-closed, openai dialect), `stored_to_env_pairs` for each dialect, R7 `validate_stored` coverage (non-loopback http, control chars in key/model/base_url, userinfo/query in base_url, unknown provider, oversized prompt, empty-key + loopback local accepted). ## Runtime integration (`desktop/src-tauri/src/managed_agents/runtime.rs`) - `build_agent_command` calls `agent_provider_settings::apply_to_command` exactly when the harness is `sprout-agent`. ACP-level vars (SPROUT_AGENT_PROVIDER etc.) are stripped from inherited parent env before injection so a stale shell `ANTHROPIC_API_KEY` never shadows saved settings. `respond-to` gate env (`SPROUT_ACP_RESPOND_TO[_ALLOWLIST]`) threads through with the new `owner_hex: Option<&str>` parameter (origin/main merge). ## Frontend - `lib/detectProvider.ts` — pure key-format detector. Recognizes Anthropic, OpenAI (legacy/proj/svcacct via fixed infix), OpenRouter, Groq, xAI, Cerebras, Together, Perplexity, Fireworks (medium), bare sk- → DeepSeek (low + ambiguity-aware), plus localhost/127.0.0.1 patterns for Ollama / vLLM / llama.cpp. Includes ADMIN_ONLY_PROVIDER_ID sentinel for `sk-ant-admin01-` which we explicitly refuse to save. Key format wins over a prefilled default base URL; an explicit non- default base URL wins back for medium-confidence keys (e.g. Fireworks + api.openai.com host). Fixture strings construct the OpenAI infix via concat so GitHub's secret scanner doesn't regex-match an inline OpenAI-shaped service-account/project key (`detectProvider.test.mjs`, `settings-agent-provider.spec.ts`). - `lib/providerCatalog.ts` — declarative catalog (id, label, dialect, isLocal, default model + base URL, key-shape hint). Drives the picker, the auto-fill on detection, the local-provider placeholder enforcement, and the per-provider model field default. - `lib/agentProviderFormState.ts` — FormState shape + reducers. `applyProviderSwitch` is the single source of truth for what gets reset on a provider change (model when empty or still previous default; baseUrl when new provider has a default OR user hasn't edited it; clears previous default host for null-default providers; drops apiKey on switch TO a local provider). Used by both the manual picker and the auto-detect effect so the policy can't drift. - `lib/agentProviderSettingsApi.ts` + `hooks/useAgentProviderSettings.ts` — typed IPC wrappers + React-Query hooks (load / save / delete / envPresence). - `ui/AgentProviderSettingsCard.tsx` — the panel itself. Empty state with shell-env hint, identity-rotation banner, load-error banner, detected-provider badge, reveal/hide toggle, advanced section, inline provider-change warning, confirm-clear dialog. On save success the plaintext is wiped from form state + reveal toggles off, independent of any React-Query refresh effect (covers the structural- sharing identical-redacted-view edge case). - `ui/AgentProviderAdvancedFields.tsx`, `AgentProviderBanners.tsx`, `AgentProviderClearDialog.tsx` — split components. ## Per-agent dialog (sprout-agent special case) - `agents/ui/CreateAgentDialogSections.tsx` — Model + System prompt inputs are hidden for sprout-agent paths (those are owned globally). A note line points users to Settings → Agent Provider. - `agents/ui/EditAgentDialog.tsx` — passes `selectedProviderId="custom"` to the shared runtime fields so the agent-command input stays editable for existing rows; the system-prompt hide still resolves via `isSproutAgentPath`'s `agentCommand` arm. - `agents/ui/ManagedAgentRow.tsx` — "Model managed by Sprout settings" link is a span with role="button" + stopPropagation (was a nested `<button>` inside the row button — both invalid HTML and double- triggering). - `agents/lib/resolveAcpProviderId.ts` — TS / Rust alignment for inline-args resolution (Rust `known_acp_provider` strips args; TS now matches). ## Tests - Rust: 328 tests passing (R7 added 7; full agent_provider_settings suite at 44/44). - TS / node-test: 55 cases for the form-state reducer + provider detector. - Playwright integration: 12 settings-agent-provider scenarios (empty state + detection + save round-trip, identity-rotation banner, provider-change-warning, key-format-beats-prefilled-baseUrl, load- error banner, clear flow + Escape cancel, rotation banner a11y, local-provider switch with saved key, manual switch reset, detected- provider model reset, post-save key-input clear). ## just ci summary - Rust: 321/321 + 7 new validate_stored tests - Mobile: 336/336 - Desktop / web: format + biome + file-size + ts-check all green - Playwright integration agents + settings-agent-provider: 19/19 green ## Codex review history - Reviews #1, #2, #3, #5, #6 surfaced and fixed: non-loopback HTTP at save AND spawn, identity-mismatch fails closed, local-provider key leak prevention, inline-args resolver alignment, Zeroize on api_key before early-return, no Debug derive, control-char / length caps + trim, owner_pubkey v2 envelope, local-provider switch unblock, EditAgentDialog command field, model reset on detection switch, nested-button fix, `applyProviderSwitch` reducer extraction. - Review #7 (P2-UI + P2-Rust): clear form.apiKey + revealKey on save success; validate decrypted settings at spawn time. - Review #8: 9/10, no blocking findings. Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com> Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
…iew findings The original implementation created a second parallel Tauri command (discover_all_acp_providers) alongside the existing one to avoid changing the return type. This produced two commands, two hooks, two query keys, and two raw type converters. Consolidates into a single command returning the full catalog, with a useAvailableAcpProviders hook that type-narrows for callers needing non-null command/binaryPath. Also fixes: pipe deadlock in install command (#1), UTF-8 truncation panic (#2/#4), adds install concurrency guard (#11), exact provider ID match (#15), error display stdout fallback (#5), success banner suppression when already available (#12), misleading re-run text (#13), IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7), configurable e2e mocks (#9), shared raw type exports (#8), and classify_provider unit tests (#10).
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
…iew findings The original implementation created a second parallel Tauri command (discover_all_acp_providers) alongside the existing one to avoid changing the return type. This produced two commands, two hooks, two query keys, and two raw type converters. Consolidates into a single command returning the full catalog, with a useAvailableAcpProviders hook that type-narrows for callers needing non-null command/binaryPath. Also fixes: pipe deadlock in install command (#1), UTF-8 truncation panic (#2/#4), adds install concurrency guard (#11), exact provider ID match (#15), error display stdout fallback (#5), success banner suppression when already available (#12), misleading re-run text (#13), IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7), configurable e2e mocks (#9), shared raw type exports (#8), and classify_provider unit tests (#10).
wpfleger96
added a commit
that referenced
this pull request
May 22, 2026
…iew findings The original implementation created a second parallel Tauri command (discover_all_acp_providers) alongside the existing one to avoid changing the return type. This produced two commands, two hooks, two query keys, and two raw type converters. Consolidates into a single command returning the full catalog, with a useAvailableAcpProviders hook that type-narrows for callers needing non-null command/binaryPath. Also fixes: pipe deadlock in install command (#1), UTF-8 truncation panic (#2/#4), adds install concurrency guard (#11), exact provider ID match (#15), error display stdout fallback (#5), success banner suppression when already available (#12), misleading re-run text (#13), IIFE refactor in PersonaDialog (#14), hidden internal query lift (#7), configurable e2e mocks (#9), shared raw type exports (#8), and classify_provider unit tests (#10).
This was referenced Aug 1, 2026
DevYonghunT
added a commit
to DevYonghunT/buzz
that referenced
this pull request
Aug 4, 2026
완료 기준 7개 중 부분 충족이던 셋(block#3 renamed, block#6 upgrade 경로, block#7 UI 실행 증거)을 충족으로 올리고 Phase 3을 완료로 표시한다. 세션 D 시점의 판정은 각 행에 그대로 남겼다 — 표를 고치고 근거를 나중에 맞추는 순서가 아니다. WORKSPACE_CATALOG.md의 두 곳을 정정했다. §7의 renamed 「구현 상태」는 이제 미리보기와 ledger 둘 다이고, 검증 표의 「데스크톱 | pnpm test | 설정 카드 렌더」는 애초에 없던 테스트를 가리키고 있었으므로 실제 Playwright 스펙으로 바꿨다. §5에는 도출식이 버전을 빼는 것을 무엇이 고정하는지 적었다. renamed의 필드 추가에는 §4의 리더-우선 순서가 적용되지 않는다는 것도 명시했다. 그 순서는 relay에 저장돼 구버전이 읽는 provenance의 steps 어휘를 위한 것이고, Ledger는 apply command의 반환값으로만 살아 생산자와 소비자가 같은 빌드 안에 있다. steps 쪽 규칙은 그대로다. E1 계획서에 세 번째 계획 이탈을 보탰다 — Task 5의 featureGate는 구현 중에 뒤집혔고(매니페스트에 없는 ID는 fail-open이라 무음 no-op) 실제 게이트는 SettingsView의 역할 검사다. 앞서 둘만 적어 목록이 불완전했다. BASELINE에는 게이트 18줄과 재주입 결과표를 남겼다. 셋 중 둘만 단독 방어선이었고, upgrade 테스트의 캔버스 단언은 아니었다 — no_change가 캔버스 단계 앞에서 반환하기 때문이다. 그 사실과, 새 Tauri command를 더하는 세션은 mock 핸들러도 함께 더해야 한다는 것을 기록했다. Phase 3 밖에 남은 것 넷은 그대로다: generation 증가 경로, 선점의 약한 형태와 위임 실행 요청, CLI 적용 경로, 나머지 8개 업무방 콘텐츠. Signed-off-by: Dev_YongT <devyongt@gmail.com>
mfethe1
pushed a commit
to mfethe1/buzz
that referenced
this pull request
Aug 8, 2026
Decision D5 of wayfinder block#7 says only agent-triggered turns consume budget. Implementing it surfaced a gap: the kind 44200 turn metric has no field naming the author that caused the turn. Its payload is harness, model, channel_id, session_id, turn_id, turn_seq, timestamp, turn, cumulative, delta_reliable and stop_reason — nothing about the trigger. TriggerLog recovers it without a wire-format change, using a property buzz-acp already guarantees: turns are serialised per channel, and all pending events for a channel drain into one batch. So the messages seen in a channel since its last turn are that turn's trigger. A batch mixing human and agent messages counts as human. D5 exists to protect the case where a person is present and watching; charging that turn risks muting an agent mid-conversation with its owner. Erring the other way costs a runaway one extra turn before it trips. Attribution is best-effort and fails open: an observation gap yields None, which is not charged, so a dropped subscription disables the budget rather than tripping it. The durable fix is a trigger field on NIP-AM, which the spec's forward-compatibility rule already permits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
mfethe1
pushed a commit
to mfethe1/buzz
that referenced
this pull request
Aug 8, 2026
The crate could account and attribute, but nothing connected it to real data. charge_from_metric decrypts a kind:44200 event with the owner's keys and extracts channel, agent, cost and end-of-turn timestamp. Kept in its own module because it is the only part of the crate that knows about Nostr. Ledger and TriggerLog stay testable on plain values with no keys and no events, which is why they have the coverage they do. Tests build real signed events with buzz-core's own encrypt helper and round-trip them, so this is checked against Buzz's actual wire format rather than a hand-rolled fixture. A stranger's key fails closed, an unrelated kind is rejected before any decrypt attempt, and missing or malformed channel ids and timestamps are errors rather than silent defaults. Notable while writing it: decrypt_agent_turn_metric already rejects negative and non-finite costUsd per NIP-AM, so the ledger's own guard against a refund is defence in depth rather than the only barrier. delta_reliable is surfaced on TurnCharge rather than swallowed — block#7 OQ7.1 asks how often it is false, and that cannot be answered if the ingest layer discards it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
mfethe1
pushed a commit
to mfethe1/buzz
that referenced
this pull request
Aug 8, 2026
Two real bugs, one security and one correctness, plus the documented standards this crate was skipping. Security: charge_from_metric took turn_end from the agent's own encrypted payload and never checked it. AgentTurnMetricPayload::validate covers only the numerics, so a metric dated in the future moved the ledger's eviction cutoff forward and wiped the pair's whole window — the budgeted agent could zero its own budget at will. The payload timestamp is now tied to the signed created_at within a 300s tolerance. Correctness: TriggerLog consumed the batch per channel rather than per agent, so with two agents in one channel whichever metric was processed first ate the other's trigger and the second turn went uncharged. Relay reordering alone disabled the budget. Consumption is now tracked per (channel, agent) with independent cursors. The spec axis found a third defect that per-agent cursors do NOT fix: the metric's timestamp is end-of-turn and the payload carries no start, so a message arriving mid-turn is attributed to the turn it did not trigger, and one owner message can free two turns. It errs toward under-charging like every other approximation here, and the durable fix is a turn-start or trigger field on NIP-AM. Documented rather than papered over — it is a second argument for block#7 OQ7.5. Both reviewers independently flagged Verdict::Allow{spent_usd: 0.0} on human and unattributed turns as a lie: the pair may hold $4.90, and a caller logging spend would see a sawtooth already collapsed to zero. Replaced with an explicit Unbudgeted variant. Standards: adds deny(unsafe_code) and warn(missing_docs) per CONTRIBUTING.md, documents the public API those lints then surfaced, and registers the crate in the AGENTS.md crate map. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013okf5qYe8U8zja6wPBAL2y
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
react-markdown, GFM, and line-break supportfeatures/messagesand remove the old static chat dataTesting
lefthookran automatically duringgit pushcargo fmt --all -- --checkpnpm checkpnpm buildcargo check --manifest-path desktop/src-tauri/Cargo.tomlcargo clippy --workspace --all-targets -- -D warnings./scripts/run-tests.sh unit